package com.back.network.netty.handlers;

import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import com.alibaba.fastjson.JSONObject;
import com.back.network.netty.ProxyChannelManager;
import com.back.network.netty.constant.Constants;
import com.back.network.netty.container.Container;
import com.back.network.netty.dto.AccountDTO;
import com.back.network.netty.dto.CommonDTO;
import com.back.network.netty.event.TransferEvent;
import com.back.network.netty.protocol.CommonEntity;
import com.back.network.netty.protocol.ProxyMessage;
import com.back.network.netty.utils.CommonUtils;
import com.back.network.web.Utils.TokenUtils;
import com.back.network.web.entity.EmailInfo;
import com.back.network.web.entity.User;
import com.back.network.web.service.MailService;
import com.back.network.web.service.UserService;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
import io.netty.channel.SimpleChannelInboundHandler;

/**
 *
 * @author fengfei
 *
 */
@Component
@Sharable
@Scope("prototype")
public class ServerChannelHandler extends SimpleChannelInboundHandler<ProxyMessage> {

	private static Logger logger = LoggerFactory.getLogger(ServerChannelHandler.class);
	@Autowired
	UserService userService;
	@Autowired
	MailService mailService;
	@Autowired
	Container container;

	@Override
	protected void channelRead0(ChannelHandlerContext ctx, ProxyMessage proxyMessage) throws Exception {

		switch (proxyMessage.getType()) {
		case ProxyMessage.C_TYPE_AUTH:
			handleAuthMessage(ctx, proxyMessage);
			break;
		case ProxyMessage.C_TYPE_OPEN:
			handleOpenMessage(ctx, proxyMessage);
			break;
		case ProxyMessage.TYPE_CONNECT:
			handleConnectMessage(ctx, proxyMessage);
			break;
		case ProxyMessage.TYPE_DISCONNECT:
			handleDisconnectMessage(ctx, proxyMessage);
			break;
		case ProxyMessage.P_TYPE_TRANSFER:
			handleTransferMessage(ctx, proxyMessage);
			break;
		case ProxyMessage.TYPE_HEARTBEAT:
			handleHeartbeatMessage(ctx, proxyMessage);
			break;
		case ProxyMessage.TYPE_SIGNUP:
			handleSignUp(ctx, proxyMessage);
			break;
		case ProxyMessage.TYPE_RESET_PASSWORD:
			handleResetPassword(ctx, proxyMessage);
			break;
		case ProxyMessage.C_TYPE_TOKEN_AUTH:
			handleTokenAuth(ctx, proxyMessage);
			break;
		case ProxyMessage.TYPE_VERIFY_CODE:
			handleVerifyCode(ctx, proxyMessage);
		default:
			break;
		}
	}

	private void handleVerifyCode(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
		String data = new String(proxyMessage.getData());
		AccountDTO accountDTO = JSONObject.parseObject(data).toJavaObject(AccountDTO.class);
		if (accountDTO.getMac().equals("signUp")) {
			signUpVerifyCode(ctx, accountDTO);
		}
		if (accountDTO.getMac().equals("resetPassword")) {
			resetPwdVerifyCode(ctx, accountDTO);
		}

	}

	private void signUpVerifyCode(ChannelHandlerContext ctx, AccountDTO accountDTO) {
		ProxyMessage message = new ProxyMessage();
		message.setType(ProxyMessage.TYPE_VERIFY_CODE);
		CommonDTO dto = CommonDTO.success();
		if (userService.isExistAccount(accountDTO)) {
			dto.setCode(CommonDTO.FAIL);
			dto.setMsg("已存在同名账户！");
		}
		if (userService.isExistEmail(accountDTO)) {
			dto.setCode(CommonDTO.FAIL);
			dto.setMsg("已存在同名邮箱！");
		}
		if (dto.getCode().equals(CommonDTO.SUCCESS)) {
			String verifyCode = CommonUtils.getRandomString(6);
			mailService.sendSignUpMail(accountDTO.getEmail(), verifyCode);
			EmailInfo emailInfo = new EmailInfo();
			emailInfo.setEmail(accountDTO.getEmail());
			emailInfo.setCode(verifyCode);
			mailService.saveMailInfo(emailInfo);
		}
		try {
			dto.setData("signUp");
			String msg = JSONObject.toJSONString(dto);
			message.setData(msg.getBytes("UTF-8"));
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		ctx.channel().writeAndFlush(message);
	}

	private void resetPwdVerifyCode(ChannelHandlerContext ctx, AccountDTO accountDTO) {
		ProxyMessage message = new ProxyMessage();
		message.setType(ProxyMessage.TYPE_VERIFY_CODE);
		CommonDTO dto = CommonDTO.success();
		String email = userService.getEmailByAccountOrEmail(accountDTO.getAccount());
		if (StringUtils.isEmpty(email)) {
			dto.setCode(CommonDTO.FAIL);
			dto.setMsg("账户或邮箱不存在！");
		}
		if (dto.getCode().equals(CommonDTO.SUCCESS)) {
			String verifyCode = CommonUtils.getRandomString(6);
			mailService.sendResetPwdMail(email, verifyCode);
			EmailInfo emailInfo = new EmailInfo();
			emailInfo.setEmail(email);
			emailInfo.setCode(verifyCode);
			mailService.saveMailInfo(emailInfo);
		}
		try {
			dto.setData("resetPassword");
			String msg = JSONObject.toJSONString(dto);
			message.setData(msg.getBytes("UTF-8"));
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		ctx.channel().writeAndFlush(message);

	}

	/**
	 * token授权
	 */
	private void handleTokenAuth(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
		String token = new String(proxyMessage.getData());
		ProxyMessage message = new ProxyMessage();
		CommonDTO dto = CommonDTO.fail();
		message.setType(ProxyMessage.C_TYPE_TOKEN_AUTH);
		if (userService.hasToken(token)) {
			ProxyChannelManager.addCmdChannel(token, ctx.channel());
			dto = CommonDTO.success();
		}
		String json = JSONObject.toJSONString(dto);
		message.setData(json.getBytes());
		ctx.channel().writeAndFlush(message);
	}

	/**
	 * 注册
	 */
	private void handleSignUp(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
		String data = new String(proxyMessage.getData());
		AccountDTO accountDTO = JSONObject.parseObject(data).toJavaObject(AccountDTO.class);
		ProxyMessage message = new ProxyMessage();
		message.setType(ProxyMessage.TYPE_SIGNUP);
		CommonDTO dto = CommonDTO.success();
		EmailInfo emailInfo = new EmailInfo();
		emailInfo.setCode(accountDTO.getMac());
		emailInfo.setEmail(accountDTO.getEmail());
		if (!mailService.checkCode(emailInfo)) {
			dto.setCode(CommonDTO.FAIL);
			dto.setMsg("验证码不正确！");
		}
		if (userService.isExistAccount(accountDTO)) {
			dto.setCode(CommonDTO.FAIL);
			dto.setMsg("已存在同名账户！");
		}
		if (userService.isExistEmail(accountDTO)) {
			dto.setCode(CommonDTO.FAIL);
			dto.setMsg("已存在同名邮箱！");
		}
		if (dto.getCode().equals(CommonDTO.SUCCESS)) {
			userService.saveAccount(accountDTO);
		}
		try {
			String msg = JSONObject.toJSONString(dto);
			message.setData(msg.getBytes("UTF-8"));
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		ctx.channel().writeAndFlush(message);
	}

	/**
	 * 重置密码
	 */
	private void handleResetPassword(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
		String data = new String(proxyMessage.getData());
		AccountDTO accountDTO = JSONObject.parseObject(data).toJavaObject(AccountDTO.class);
		ProxyMessage message = new ProxyMessage();
		message.setType(ProxyMessage.TYPE_RESET_PASSWORD);
		CommonDTO dto = CommonDTO.success();
		String email = userService.getEmailByAccountOrEmail(accountDTO.getAccount());
		EmailInfo emailInfo = new EmailInfo();
		emailInfo.setCode(accountDTO.getMac());
		emailInfo.setEmail(email);
		if (StringUtils.isEmpty(email)) {
			dto.setCode(CommonDTO.FAIL);
			dto.setMsg("账户或邮箱不存在！");
		}
		if (dto.getCode().equals(CommonDTO.SUCCESS)&&!mailService.checkCode(emailInfo)) {
			dto.setCode(CommonDTO.FAIL);
			dto.setMsg("验证码不正确！");
		}
		if (dto.getCode().equals(CommonDTO.SUCCESS)) {
			userService.updatePasswordByEmailOrAccount(accountDTO.getPassword(),accountDTO.getAccount());
		}
		try {
			String msg = JSONObject.toJSONString(dto);
			message.setData(msg.getBytes("UTF-8"));
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		ctx.channel().writeAndFlush(message);
	}

	/** 开启用户服务 **/
	private void handleOpenMessage(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
		String dataStr = new String(proxyMessage.getData());
		CommonEntity entity = JSONObject.parseObject(dataStr).toJavaObject(CommonEntity.class);
		if (userService.hasToken(entity.getToken())) {
			// 代理信息。可入库做分析
			// String lanInfo = entity.getLanInfo();
			// container.startUserService(1573);
			// service.saveUserPort(entity.getToken(), 1573);
		} else {
			logger.info("token 无效");
			// 强制客户端重新登录校验。
		}

	}

	private void handleTransferMessage(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
		Channel userChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
		if (userChannel != null) {
			ByteBuf buf = ctx.alloc().buffer(proxyMessage.getData().length);
			buf.writeBytes(proxyMessage.getData());
			userChannel.writeAndFlush(buf);
		}
	}

	private void handleDisconnectMessage(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
		String clientKey = ctx.channel().attr(Constants.TOKEN).get();

		// 代理连接没有连上服务器由控制连接发送用户端断开连接消息
		if (clientKey == null) {
			String userId = proxyMessage.getUri();
			Channel userChannel = ProxyChannelManager.removeUserChannelFromCmdChannel(ctx.channel(), userId);
			if (userChannel != null) {
				// 数据发送完成后再关闭连接，解决http1.0数据传输问题
				userChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
			}
			return;
		}

		Channel cmdChannel = ProxyChannelManager.getCmdChannel(clientKey);
		if (cmdChannel == null) {
			logger.warn("ConnectMessage:error cmd channel key {}", ctx.channel().attr(Constants.TOKEN).get());
			return;
		}

		Channel userChannel = ProxyChannelManager.removeUserChannelFromCmdChannel(cmdChannel,
				ctx.channel().attr(Constants.USER_ID).get());
		if (userChannel != null) {
			// 数据发送完成后再关闭连接，解决http1.0数据传输问题
			userChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
			ctx.channel().attr(Constants.NEXT_CHANNEL).remove();
			ctx.channel().attr(Constants.TOKEN).remove();
			ctx.channel().attr(Constants.USER_ID).remove();
		}
	}

	private void handleConnectMessage(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
		String uri = proxyMessage.getUri();
		if (uri == null) {
			ctx.channel().close();
			logger.warn("ConnectMessage:null uri");
			return;
		}

		String[] tokens = uri.split("@");
		if (tokens.length != 2) {
			ctx.channel().close();
			logger.warn("ConnectMessage:error uri");
			return;
		}
		// 通过客户端秘钥获取信道
		Channel cmdChannel = ProxyChannelManager.getCmdChannel(tokens[1]);
		if (cmdChannel == null) {
			ctx.channel().close();
			logger.warn("ConnectMessage:error cmd channel key {}", tokens[1]);
			return;
		}

		// 通过客户端信道 及 用户id 获取 用户信道
		Channel userChannel = ProxyChannelManager.getUserChannel(cmdChannel, tokens[0]);
		if (userChannel != null) {
			ctx.channel().attr(Constants.USER_ID).set(tokens[0]);
			ctx.channel().attr(Constants.TOKEN).set(tokens[1]);
			ctx.channel().attr(Constants.NEXT_CHANNEL).set(userChannel);
			userChannel.attr(Constants.NEXT_CHANNEL).set(ctx.channel());

			userChannel.pipeline().fireUserEventTriggered(new TransferEvent());
			// 代理客户端与后端服务器连接成功，修改用户连接为可读状态
			userChannel.config().setOption(ChannelOption.AUTO_READ, true);
		}
	}

	private void handleHeartbeatMessage(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
		ProxyMessage heartbeatMessage = new ProxyMessage();
		heartbeatMessage.setSerialNumber(heartbeatMessage.getSerialNumber());
		heartbeatMessage.setType(ProxyMessage.TYPE_HEARTBEAT);
		logger.debug("response heartbeat message {}", ctx.channel());
		ctx.channel().writeAndFlush(heartbeatMessage);
	}

	/**
	 * 建立客户端信道和端口，秘钥映射
	 */
	private void handleAuthMessage(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {

		String data = new String(proxyMessage.getData());
		CommonEntity entity = JSONObject.parseObject(data).toJavaObject(CommonEntity.class);
		if (StringUtils.isEmpty(entity.getToken())) {
			User user = new User();
			user.setAccount(entity.getAccount());
			user.setPassword(entity.getPassword());
			User userInfo = userService.getUser(user);

			ProxyMessage message = new ProxyMessage();
			message.setType(ProxyMessage.C_TYPE_AUTH);
			// 登录成功
			if (null != userInfo) {
				InetSocketAddress inetSocketAddress = (InetSocketAddress) ctx.channel().remoteAddress();
				String clientIp = inetSocketAddress.getAddress().getHostAddress();
				String token = TokenUtils.getInstance().makeToken();
				// 绑定账户ID-域名-Token
				userService.saveUserToken(userInfo, token);
				entity.setPassword("");
				entity.setAccount(userInfo.getAccount());
				entity.setToken(token);
				entity.setDomain(userInfo.getDomain());
				entity.setClientIp(clientIp);
				String str = JSONObject.toJSONString(entity);
				message.setData(str.getBytes());
				ProxyChannelManager.addCmdChannel(token, ctx.channel());
			} else {
				// 登录失败
				message.setData("fail".getBytes());
			}
			ctx.channel().writeAndFlush(message);
			return;
		}

	}

	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		super.channelActive(ctx);
	}

	@Override
	public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
		Channel userChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
		if (userChannel != null) {
			userChannel.config().setOption(ChannelOption.AUTO_READ, ctx.channel().isWritable());
		}

		super.channelWritabilityChanged(ctx);
	}

	/**
	 * 信道关闭则移除已保存的映射
	 */
	@Override
	public void channelInactive(ChannelHandlerContext ctx) throws Exception {
		Channel userChannel = ctx.channel().attr(Constants.NEXT_CHANNEL).get();
		if (userChannel != null && userChannel.isActive()) {
			String clientKey = ctx.channel().attr(Constants.TOKEN).get();
			String userId = ctx.channel().attr(Constants.USER_ID).get();
			Channel cmdChannel = ProxyChannelManager.getCmdChannel(clientKey);
			if (cmdChannel != null) {
				ProxyChannelManager.removeUserChannelFromCmdChannel(cmdChannel, userId);
			} else {
				logger.warn("null cmdChannel, clientKey is {}", clientKey);
			}

			// 数据发送完成后再关闭连接，解决http1.0数据传输问题
			userChannel.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
			userChannel.close();
		} else {
			ProxyChannelManager.removeCmdChannel(ctx.channel());
		}

		super.channelInactive(ctx);
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		logger.info("exception caught", cause);
		super.exceptionCaught(ctx, cause);
	}
}